<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/%E6%AD%A6%E6%B1%8932x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/%E6%AD%A6%E6%B1%8916x16.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib1/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib1/pace/pace-theme-center-simple.min.css">
  <script src="/lib1/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"example.com","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="前言本博客参考曾探所著&lt;&lt;JavaScript设计模式与开发实践&gt;&gt;，仅为个人学习总结，侵删。 设计模式原则单一职责原则单一职责原则(Single Responsibility Principle,SRP)：一个类只负责一个功能领域中的相应职责，或者可以定义为：就一个类而言，应该只有一个引起它变化的原因。 单一职责原则是实现高内聚、低耦合的指导方针，它是最简单但又最难运用的">
<meta property="og:type" content="article">
<meta property="og:title" content="JavaScript的设计模式（二）">
<meta property="og:url" content="http://example.com/2021/10/31/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%BA%8C%EF%BC%89/index.html">
<meta property="og:site_name" content="Technological Blog">
<meta property="og:description" content="前言本博客参考曾探所著&lt;&lt;JavaScript设计模式与开发实践&gt;&gt;，仅为个人学习总结，侵删。 设计模式原则单一职责原则单一职责原则(Single Responsibility Principle,SRP)：一个类只负责一个功能领域中的相应职责，或者可以定义为：就一个类而言，应该只有一个引起它变化的原因。 单一职责原则是实现高内聚、低耦合的指导方针，它是最简单但又最难运用的">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2021-10-31T04:18:39.000Z">
<meta property="article:modified_time" content="2021-11-03T08:15:31.470Z">
<meta property="article:author" content="Li Yudong">
<meta property="article:tag" content="设计模式">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="http://example.com/2021/10/31/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%BA%8C%EF%BC%89/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>JavaScript的设计模式（二） | Technological Blog</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">Technological Blog</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">IT小白的成长之旅</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="tags fa-fw"></i>标签<span class="badge">18</span></a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="th fa-fw"></i>分类<span class="badge">14</span></a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="archive fa-fw"></i>归档<span class="badge">95</span></a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

  <a href="https://github.com/tiarmor1" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/10/31/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%BA%8C%EF%BC%89/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          JavaScript的设计模式（二）
        </h1>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-10-31 12:18:39" itemprop="dateCreated datePublished" datetime="2021-10-31T12:18:39+08:00">2021-10-31</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:15:31" itemprop="dateModified" datetime="2021-11-03T16:15:31+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" itemprop="url" rel="index"><span itemprop="name">设计模式</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
        <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>本博客参考曾探所著&lt;&lt;JavaScript设计模式与开发实践&gt;&gt;，仅为个人学习总结，侵删。</p>
<h1 id="设计模式原则"><a href="#设计模式原则" class="headerlink" title="设计模式原则"></a>设计模式原则</h1><h2 id="单一职责原则"><a href="#单一职责原则" class="headerlink" title="单一职责原则"></a>单一职责原则</h2><p><strong>单一职责原则(Single Responsibility Principle,SRP)：一个类只负责一个功能领域中的相应职责，或者可以定义为：就一个类而言，应该只有一个引起它变化的原因。</strong> 单一职责原则是实现高内聚、低耦合的指导方针，它是最简单但又最难运用的原则，需要设计人员发现类的不同职责并将其分离，而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。 </p>
<h2 id="开放封闭原则"><a href="#开放封闭原则" class="headerlink" title="开放封闭原则"></a>开放封闭原则</h2><p> <strong>开闭原则(Open-Closed Principle,OCP)：一个软件实体应当对扩展开放，对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。</strong> 开闭原则是面向对象的可复用设计的第一块基石，它是最重要的面向对象设计原则。在开闭原则的定义中，软件实体可以指一个软件模块、一个由多个类组成的局部结构或一个独立的类。 </p>
<p>为了满足开闭原则，需要对系统进行抽象化设计，抽象化是开闭原则的关键。在Java、C#等编程语言中，可以为系统定义一个相对稳定的抽象层，而将不同的实现行为移至具体的实现层中完成。在很多面向对象编程语言中都提供了接口、抽象类等机制，可以通过它们定义系统的抽象层，再通过具体类来进行扩展。如果需要修改系统的行为，无须对抽象层进行任何改动，只需要增加新的具体类来实现新的业务功能即可，实现在不修改已有代码的基础上扩展系统的功能，达到开闭原则的要求。 </p>
<p>因为xml和properties等格式的配置文件是纯文本文件，可以直接通过编辑器进行编辑，且无须编译，因此在软件开发中，一般不把对配置文件的修改认为是对系统源代码的修改。如果一个系统在扩展时只涉及到修改配置文件，而原有的Java代码或C#代码没有做任何修改，该系统即可认为是一个符合开闭原则的系统。 </p>
<h2 id="里氏代换原则"><a href="#里氏代换原则" class="headerlink" title="里氏代换原则"></a>里氏代换原则</h2><p><strong>里氏代换原则(Liskov Substitution Principle,LSP)：所有引用基类（父类）的地方必须能透明地使用其子类的对象。 正式由于子类型的可替换性，才使得使用父类类型的模块在无需修改的情况下就可以进行扩展。</strong></p>
<p>里氏代换原则是实现开闭原则的重要方式之一，由于使用基类对象的地方都可以使用子类对象，因此在程序中尽量使用基类类型来对对象进行定义，而在运行时再确定其子类类型，用子类对象来替换父类对象。在使用里氏代换原则时需要注意如下几个问题： </p>
<p>(1)：子类的所有方法必须在父类中声明，或子类必须实现父类中声明的所有方法。根据里氏代换原则，为了保证系统的扩展性，在程序中通常使用父类来进行定义，如果一个方法只存在子类中，在父类中不提供相应的声明，则无法在以父类定义的对象中使用该方法。 </p>
<p>(2)：在运用里氏代换原则时，尽量把父类设计为抽象类或者接口，让子类继承父类或实现父接口，并实现在父类中声明的方法，运行时，子类实例替换父类实例，可以很方便地扩展系统的功能，同时无须修改原有子类的代码，增加新的功能可以通过增加一个新的子类来实现。 </p>
<h2 id="依赖倒转原则"><a href="#依赖倒转原则" class="headerlink" title="依赖倒转原则"></a>依赖倒转原则</h2><p><strong>依赖倒转原则(Dependency Inversion Principle,DIP)：抽象不应该依赖于细节，细节应当依赖于抽象。换言之，要针对接口编程，而不是针对实现编程。（高层模块不应该依赖低层模块，两个都应该依赖抽象）。</strong></p>
<p>依赖倒转原则是面向对象设计的主要实现机制之一，它是系统抽象化的具体实现。 依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中，尽量引用层次高的抽象层类，即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明，以及数据类型的转换等，而不要用具体类来做这些事情。</p>
<p>为了确保该原则的应用，一个具体类应当只实现接口或抽象类中声明过的方法，而不要给出多余的方法，否则将无法调用到在子类中增加的新方法。 在实现依赖倒转原则时，需要针对抽象层编程，而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中，依赖注入是指当一个对象要与其他对象发生依赖关系时，通过抽象来注入所依赖的对象。</p>
<p>常用的注入方式有三种，分别是：构造注入，设值注入（Setter注入）和接口注入。构造注入是指通过构造函数来传入具体类的对象，设值注入是指通过Setter方法来传入具体类的对象，而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型，在运行时再传入具体类型的对象，由子类对象来覆盖父类对象。 依赖倒转其实可以说是面向对象设计的标志，如果编写的时候考虑的都是如何针对抽象编程而不是针对细节编程，即程序中所有的依赖关系都是终止于抽象类或者接口，则为面向对象的设计。 </p>
<h2 id="接口隔离原则"><a href="#接口隔离原则" class="headerlink" title="接口隔离原则"></a>接口隔离原则</h2><p><strong>接口隔离原则(Interface Segregation Principle,ISP)：使用多个专门的接口，而不使用单一的总接口，即客户端不应该依赖那些它不需要的接口。</strong> </p>
<p>根据接口隔离原则，当一个接口太大时，我们需要将它分割成一些更细小的接口，使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色，不干不该干的事，该干的事都要干。这里的“接口”往往有两种不同的含义：一种是指一个类型所具有的方法特征的集合，仅仅是一种逻辑上的抽象；另外一种是指某种语言具体的“接口”定义，有严格的定义和结构，比如Java语言中的interface。对于这两种不同的含义，ISP的表达方式以及含义都有所不同： </p>
<p>(1)： 当把“接口”理解成一个类型所提供的所有方法特征的集合的时候，这就是一种逻辑上的概念，接口的划分将直接带来类型的划分。可以把接口理解成角色，一个接口只能代表一个角色，每个角色都有它特定的一个接口，此时，这个原则可以叫做“角色隔离原则”。</p>
<p>(2)： 如果把“接口”理解成狭义的特定语言的接口，那么ISP表达的意思是指接口仅仅提供客户端需要的行为，客户端不需要的行为则隐藏起来，应当为客户端提供尽可能小的单独的接口，而不要提供大的总接口，比如实现中还有还有自己的一些封装的功能方法，而对于每一个接口实现类，可能这些功能代码可能不同。在面向对象编程语言中，实现一个接口就需要实现该接口中定义的所有方法，因此大的总接口使用起来不一定很方便，为了使接口的职责单一，需要将大接口中的方法根据其职责不同分别放在不同的小接口中，以确保每个接口使用起来都较为方便，并都承担某一单一角色。</p>
<p>接口应该尽量细化，同时接口中的方法应该尽量少，每个接口中只包含一个客户端（如子模块或业务逻辑类）所需的方法即可，这种机制也称为“定制服务”，即为不同的客户端提供宽窄不同的接口。 在使用接口隔离原则时，需要注意控制接口的粒度，接口不能太小，如果太小会导致系统中接口泛滥，不利于维护；接口也不能太大，太大的接口将违背接口隔离原则，灵活性较差，使用起来很不方便。 </p>
<h2 id="迪米特法则"><a href="#迪米特法则" class="headerlink" title="迪米特法则"></a>迪米特法则</h2><p><strong>迪米特法则(Law of Demeter,LoD)：又叫作最少知道原则（Least Knowledge Principle, LKP），一个软件实体应当尽可能少地与其他实体发生相互作用。</strong>就是说一个对象应当对其他对象有尽可能少的了解，不和陌生人说话。 如果两个类不必彼此直接通信，那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话，可以通过第三者转发这个调用。 </p>
<p>迪米特法则可降低系统的耦合度，使类与类之间保持松散的耦合关系。迪米特法则可以简单说成：talk only to your immediate friends。 对于LOD来说，又被解释为下面几种方式：一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识，而且局限于那些与本单位密切相关的软件单位。 迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖，因此，很容易使得系统的功能模块功能独立，相互之间不存在（或很少有）依赖关系。 </p>
<p>迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系，也希望能通过它的友元类来转达。因此，应用迪米特法则有可能造成的一个后果就是：系统中存在大量的中介类，这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。 设计模式的门面模式（Facade）和中介模式（Mediator），都是应用的例子。 </p>
<h1 id="迭代器模式"><a href="#迭代器模式" class="headerlink" title="迭代器模式"></a>迭代器模式</h1><p>迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素，而又不需要暴露该对象 的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来，在使用迭代器模式之后，即 使不关心对象的内部构造，也可以按顺序访问其中的每个元素。</p>
<p>现在我们来自己实现一个 each 函数，each 函数接受 2 个参数，第一个为被循环的数组，第 二个为循环中的每一步后将被触发的回调函数：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> each = <span class="function"><span class="keyword">function</span>(<span class="params"> ary, callback </span>)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = ary.length; i &lt; l; i++ )&#123; </span><br><span class="line">        callback.call( ary[i], i, ary[ i ] ); <span class="comment">// 把下标和元素当作参数传给 callback 函数</span></span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line">each( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ], <span class="function"><span class="keyword">function</span>(<span class="params"> i, n </span>)</span>&#123; </span><br><span class="line">    alert ( [ i, n ] ); </span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="内部、外部迭代器"><a href="#内部、外部迭代器" class="headerlink" title="内部、外部迭代器"></a>内部、外部迭代器</h2><h3 id="内部迭代"><a href="#内部迭代" class="headerlink" title="内部迭代"></a>内部迭代</h3><p>我们刚刚编写的 each 函数属于内部迭代器，each 函数的内部已经定义好了迭代规则，它完 全接手整个迭代过程，外部只需要一次初始调用。 内部迭代器在调用的时候非常方便，外界不用关心迭代器内部的实现，跟迭代器的交互也仅 仅是一次初始调用，但这也刚好是内部迭代器的缺点。由于内部迭代器的迭代规则已经被提前规 定，上面的 each 函数就无法同时迭代 2 个数组了。</p>
<p>比如现在有个需求，要判断 2 个数组里元素的值是否完全相等， 如果不改写 each 函数本身 的代码，我们能够入手的地方似乎只剩下 each 的回调函数了，代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> compare = <span class="function"><span class="keyword">function</span>(<span class="params"> ary1, ary2 </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( ary1.length !== ary2.length )&#123; </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span> ( <span class="string">'ary1 和 ary2 不相等'</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">    each( ary1, <span class="function"><span class="keyword">function</span>(<span class="params"> i, n </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">if</span> ( n !== ary2[ i ] )&#123; </span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span> ( <span class="string">'ary1 和 ary2 不相等'</span> ); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125;); </span><br><span class="line">    alert ( <span class="string">'ary1 和 ary2 相等'</span> ); </span><br><span class="line">&#125;; </span><br><span class="line">compare( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ], [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">4</span> ] ); <span class="comment">// throw new Error ( 'ary1 和 ary2 不相等' );</span></span><br></pre></td></tr></table></figure>

<p>说实话，这个 compare 函数一点都算不上好看，我们目前能够顺利完成需求，还要感谢在 JavaScript 里可以把函数当作参数传递的特性，但在其他语言中未必就能如此幸运。在一些没有闭包的语言中，内部迭代器本身的实现也相当复杂。比如 C 语言中的内部迭代器 是用函数指针来实现的，循环处理所需要的数据都要以参数的形式明确地从外面传递进去。</p>
<h3 id="外部迭代"><a href="#外部迭代" class="headerlink" title="外部迭代"></a>外部迭代</h3><p>外部迭代器必须显式地请求迭代下一个元素。 外部迭代器增加了一些调用的复杂度，但相对也增强了迭代器的灵活性，我们可以手工控制 迭代的过程或者顺序。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Iterator = <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> current = <span class="number">0</span>; </span><br><span class="line">    <span class="keyword">var</span> next = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        current += <span class="number">1</span>; </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">var</span> isDone = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">return</span> current &gt;= obj.length; </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">var</span> getCurrItem = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">return</span> obj[ current ]; </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        next: next, </span><br><span class="line">        isDone: isDone, </span><br><span class="line">        getCurrItem: getCurrItem </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="comment">//再看看该如何改写compare函数</span></span><br><span class="line"><span class="keyword">var</span> compare = <span class="function"><span class="keyword">function</span>(<span class="params"> iterator1, iterator2 </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">while</span>( !iterator1.isDone() &amp;&amp; !iterator2.isDone() )&#123; </span><br><span class="line">        <span class="keyword">if</span> ( iterator1.getCurrItem() !== iterator2.getCurrItem() )&#123; </span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span> ( <span class="string">'iterator1 和 iterator2 不相等'</span> ); </span><br><span class="line">        &#125; </span><br><span class="line">        iterator1.next(); </span><br><span class="line">        iterator2.next(); </span><br><span class="line">    &#125; </span><br><span class="line">    alert ( <span class="string">'iterator1 和 iterator2 相等'</span> ); </span><br><span class="line">&#125; </span><br><span class="line"><span class="keyword">var</span> iterator1 = Iterator( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ] ); </span><br><span class="line"><span class="keyword">var</span> iterator2 = Iterator( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ] ); </span><br><span class="line">compare( iterator1, iterator2 ); <span class="comment">// 输出：iterator1 和 iterator2 相等</span></span><br></pre></td></tr></table></figure>

<p>外部迭代器虽然调用方式相对复杂，但它的适用面更广，也能满足更多变的需求。内部迭代 器和外部迭代器在实际生产中没有优劣之分，究竟使用哪个要根据需求场景而定。</p>
<h2 id="迭代器应用"><a href="#迭代器应用" class="headerlink" title="迭代器应用"></a>迭代器应用</h2><p>在不同的浏览器环境下，选择的上传方式是不一样的。因为使用浏览器的上传控件进行上传 速度快，可以暂停和续传，所以我们首先会优先使用控件上传。如果浏览器没有安装上传控件， 则使用 Flash 上传， 如果连 Flash 也没安装，那就只好使用浏览器原生的表单上传了。</p>
<p>为了得到一个 upload 对象，这个 getUploadObj 函数里面充斥了 try，catch 以及 if 条件分支。缺点是显而易见的。第一是很难阅读，第二是严重违反开闭原则。 在开发和 调试过程中，我们需要来回切换不同的上传方式，每次改动都相当痛苦。后来我们还增加支持了 一些另外的上传方式，比如，HTML5 上传，这时候唯一的办法是继续往 getUploadObj 函数里增 加条件分支。 </p>
<p>现在来梳理一下问题，目前一共有 3 种可能的上传方式，我们不知道目前正在使用的浏览器 支持哪几种。就好比我们有一个钥匙串，其中共有 3 把钥匙，我们想打开一扇门但是不知道该使 用哪把钥匙，于是从第一把钥匙开始，迭代钥匙串进行尝试，直到找到了正确的钥匙为止。  同样，我们把每种获取 upload 对象的方法都封装在各自的函数里，然后使用一个迭代器， 迭代获取这些 upload 对象，直到获取到一个可用的为止：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> getActiveUploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">try</span>&#123; </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ActiveXObject( <span class="string">"TXFTNActiveX.FTNUpload"</span> ); <span class="comment">// IE 上传控件</span></span><br><span class="line">    &#125;<span class="keyword">catch</span>(e)&#123; </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> getFlashUploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( supportFlash() )&#123; <span class="comment">// supportFlash 函数未提供</span></span><br><span class="line">        <span class="keyword">var</span> str = <span class="string">'&lt;object type="application/x-shockwave-flash"&gt;&lt;/object&gt;'</span>; </span><br><span class="line">        <span class="keyword">return</span> $( str ).appendTo( $(<span class="string">'body'</span>) ); </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> getFormUpladObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> str = <span class="string">'&lt;input name="file" type="file" class="ui-file"/&gt;'</span>; <span class="comment">// 表单上传</span></span><br><span class="line">    <span class="keyword">return</span> $( str ).appendTo( $(<span class="string">'body'</span>) ); </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>在 getActiveUploadObj、getFlashUploadObj、getFormUpladObj 这 3 个函数中都有同一个约定： 如果该函数里面的 upload 对象是可用的，则让函数返回该对象，反之返回 false，提示迭代器继 续往后面进行迭代。 所以我们的迭代器只需进行下面这几步工作。</p>
<p>1、提供一个可以被迭代的方法，使得 getActiveUploadObj，getFlashUploadObj 以及 getFlashUploadObj 依照优先级被循环迭代。</p>
<p>2、如果正在被迭代的函数返回一个对象，则表示找到了正确的 upload 对象，反之如果该函 数返回 false，则让迭代器继续工作。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> iteratorUploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, fn; fn = <span class="built_in">arguments</span>[ i++ ]; )&#123; </span><br><span class="line">        <span class="keyword">var</span> uploadObj = fn(); </span><br><span class="line">        <span class="keyword">if</span> ( uploadObj !== <span class="literal">false</span> )&#123; </span><br><span class="line">            <span class="keyword">return</span> uploadObj; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> uploadObj = iteratorUploadObj( getActiveUploadObj, getFlashUploadObj, getFormUpladObj );</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">重构代码之后，我们可以看到，获取不同上传对象的方法被隔离在各自的函数里互不干扰，</span></span><br><span class="line"><span class="comment">try、catch 和 if 分支不再纠缠在一起，使得我们可以很方便地的维护和扩展代码。比如，后来</span></span><br><span class="line"><span class="comment">我们又给上传项目增加了 Webkit 控件上传和 HTML5 上传，我们要做的仅仅是下面一些工作。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="comment">//1、增加分别获取 Webkit 控件上传对象和 HTML5 上传对象的函数：</span></span><br><span class="line"><span class="keyword">var</span> getWebkitUploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="comment">// 具体代码略</span></span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> getHtml5UploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="comment">// 具体代码略</span></span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">//2、依照优先级把它们添加进迭代器：</span></span><br><span class="line"><span class="keyword">var</span> uploadObj = iteratorUploadObj( getActiveUploadObj, getWebkitUploadObj, getFlashUploadObj, getHtml5UploadObj, getFormUpladObj );</span><br></pre></td></tr></table></figure>

<p>迭代器模式是一种相对简单的模式，简单到很多时候我们都不认为它是一种设计模式。目前 的绝大部分语言都内置了迭代器</p>
<h1 id="发布订阅模式"><a href="#发布订阅模式" class="headerlink" title="发布订阅模式"></a>发布订阅模式</h1><p>在刚刚的例子中，发送短信通知就是一个典型的发布—订阅模式，小明、小红等购买者都是 订阅者，他们订阅了房子开售的消息。售楼处作为发布者，会在合适的时候遍历花名册上的电话 号码，依次给购房者发布消息。可以发现，在这个例子中使用发布—订阅模式有着显而易见的优点。 </p>
<p>1、购房者不用再天天给售楼处打电话咨询开售时间，在合适的时间点，售楼处作为发布者 会通知这些消息订阅者。 2、 购房者和售楼处之间不再强耦合在一起，当有新的购房者出现时，他只需把手机号码留 在售楼处，售楼处不关心购房者的任何情况，不管购房者是男是女还是一只猴子。 而售 楼处的任何变动也不会影响购买者，比如售楼 MM 离职，售楼处从一楼搬到二楼，这些 改变都跟购房者无关，只要售楼处记得发短信这件事情</p>
<p>第一点说明发布—订阅模式可以广泛应用于异步编程中，这是一种替代传递回调函数的方案。 比如，我们可以订阅 ajax 请求的 error、succ 等事件。或者如果想在动画的每一帧完成之后做一 些事情，那我们可以订阅一个事件，然后在动画的每一帧完成之后发布这个事件。在异步编程中 使用发布—订阅模式，我们就无需过多关注对象在异步运行期间的内部状态，而只需要订阅感兴 趣的事件发生点。 </p>
<p>第二点说明发布—订阅模式可以取代对象之间硬编码的通知机制，一个对象不用再显式地调 用另外一个对象的某个接口。发布—订阅模式让两个对象松耦合地联系在一起，虽然不太清楚彼 此的细节，但这不影响它们之间相互通信。当有新的订阅者出现时，发布者的代码不需要任何修 改；同样发布者需要改变时，也不会影响到之前的订阅者。只要之前约定的事件名没有变化，就 可以自由地改变它们</p>
<p>实现发布订阅模式的步骤：</p>
<p>1、 首先要指定好谁充当发布者（比如售楼处）；2、然后给发布者添加一个缓存列表，用于存放回调函数以便通知订阅者（售楼处的花名册）；3、最后发布消息的时候，发布者会遍历这个缓存列表，依次触发里面存放的订阅者回调函 数（遍历花名册，挨个发短信）</p>
<p>另外，我们还可以往回调函数里填入一些参数，订阅者可以接收这些参数。这是很有必要的， 比如售楼处可以在发给订阅者的短信里加上房子的单价、面积、容积率等信息，订阅者接收到这 些信息之后可以进行各自的处理。下面，我们简单实现一个自己的发布订阅模式：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> salesOffices = &#123;&#125;; <span class="comment">// 定义售楼处</span></span><br><span class="line">salesOffices.clientList = &#123;&#125;; <span class="comment">// 缓存列表，存放订阅者的回调函数</span></span><br><span class="line">salesOffices.listen = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( !<span class="keyword">this</span>.clientList[ key ] )&#123; <span class="comment">// 如果还没有订阅过此类消息，给该类消息创建一个缓存列表</span></span><br><span class="line">        <span class="keyword">this</span>.clientList[ key ] = []; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">this</span>.clientList[ key ].push( fn ); <span class="comment">// 订阅的消息添加进消息缓存列表</span></span><br><span class="line">&#125;; </span><br><span class="line">salesOffices.trigger = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; <span class="comment">// 发布消息</span></span><br><span class="line">    <span class="keyword">var</span> key = <span class="built_in">Array</span>.prototype.shift.call( <span class="built_in">arguments</span> ), <span class="comment">// 取出消息类型</span></span><br><span class="line">        fns = <span class="keyword">this</span>.clientList[ key ]; <span class="comment">// 取出该消息对应的回调函数集合</span></span><br><span class="line">    <span class="keyword">if</span> ( !fns || fns.length === <span class="number">0</span> )&#123; <span class="comment">// 如果没有订阅该消息，则返回</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">for</span>( <span class="keyword">var</span> i = <span class="number">0</span>, fn; fn = fns[ i++ ]; )&#123; </span><br><span class="line">        fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); <span class="comment">// (2) // arguments 是发布消息时附送的参数</span></span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line">salesOffices.listen( <span class="string">'squareMeter88'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>&#123; <span class="comment">// 小明订阅 88 平方米房子的消息</span></span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); <span class="comment">// 输出： 2000000 </span></span><br><span class="line">&#125;); </span><br><span class="line">salesOffices.listen( <span class="string">'squareMeter110'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>&#123; <span class="comment">// 小红订阅 110 平方米房子的消息</span></span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); <span class="comment">// 输出： 3000000 </span></span><br><span class="line">&#125;); </span><br><span class="line">salesOffices.trigger( <span class="string">'squareMeter88'</span>, <span class="number">2000000</span> ); <span class="comment">// 发布 88 平方米房子的价格</span></span><br><span class="line">salesOffices.trigger( <span class="string">'squareMeter110'</span>, <span class="number">3000000</span> ); <span class="comment">// 发布 110 平方米房子的价格</span></span><br></pre></td></tr></table></figure>

<p>现在我们已经看到了如何让售楼处拥有接受订阅和发布事件的功能。假设现在小明又去另一个售楼处买房子，那么这段代码是否必须在另一个售楼处对象上重写一次呢，有没有办法可以让 所有对象都拥有发布—订阅功能呢？ 答案显然是有的，JavaScript 作为一门解释执行的语言，给对象动态添加职责是理所当然的 事情。 所以我们把发布—订阅的功能提取出来，放在一个单独的对象内：</p>
<h2 id="通用实现"><a href="#通用实现" class="headerlink" title="通用实现"></a>通用实现</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//我们把发布—订阅的功能提取出来，放在一个单独的对象内：</span></span><br><span class="line"><span class="keyword">var</span> event = &#123; </span><br><span class="line">    clientList: [], </span><br><span class="line">    listen: <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">if</span> ( !<span class="keyword">this</span>.clientList[ key ] )&#123; </span><br><span class="line">            <span class="keyword">this</span>.clientList[ key ] = []; </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">this</span>.clientList[ key ].push( fn ); <span class="comment">// 订阅的消息添加进缓存列表</span></span><br><span class="line">    &#125;, </span><br><span class="line">    trigger: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">var</span> key = <span class="built_in">Array</span>.prototype.shift.call( <span class="built_in">arguments</span> ), <span class="comment">// (1); </span></span><br><span class="line">            fns = <span class="keyword">this</span>.clientList[ key ]; </span><br><span class="line">        <span class="keyword">if</span> ( !fns || fns.length === <span class="number">0</span> )&#123; <span class="comment">// 如果没有绑定对应的消息</span></span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">for</span>( <span class="keyword">var</span> i = <span class="number">0</span>, fn; fn = fns[ i++ ]; )&#123; </span><br><span class="line">            fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); <span class="comment">// (2) // arguments 是 trigger 时带上的参数</span></span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">//再定义一个 installEvent 函数，这个函数可以给所有的对象都动态安装发布—订阅功能：关键在于这个动态添加的使用</span></span><br><span class="line"><span class="keyword">var</span> installEvent = <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i <span class="keyword">in</span> event )&#123; </span><br><span class="line">        obj[ i ] = event[ i ]; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//再来测试一番，我们给售楼处对象 salesOffices 动态增加发布—订阅功能：</span></span><br><span class="line"><span class="keyword">var</span> salesOffices = &#123;&#125;; </span><br><span class="line">installEvent( salesOffices ); </span><br><span class="line">salesOffices.listen( <span class="string">'squareMeter88'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>&#123; <span class="comment">// 小明订阅消息</span></span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); </span><br><span class="line">&#125;); </span><br><span class="line">salesOffices.listen( <span class="string">'squareMeter100'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>&#123; <span class="comment">// 小红订阅消息</span></span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); </span><br><span class="line">&#125;); </span><br><span class="line">salesOffices.trigger( <span class="string">'squareMeter88'</span>, <span class="number">2000000</span> ); <span class="comment">// 输出：2000000 </span></span><br><span class="line">salesOffices.trigger( <span class="string">'squareMeter100'</span>, <span class="number">3000000</span> ); <span class="comment">// 输出：3000000</span></span><br></pre></td></tr></table></figure>

<p>有时候，我们也许需要取消订阅事件的功能。比如小明突然不想买房子了，为了避免继续接 收到售楼处推送过来的短信，小明需要取消之前订阅的事件。现在我们给 event 对象增加 remove 方法</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">event.remove = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> fns = <span class="keyword">this</span>.clientList[ key ]; </span><br><span class="line">    <span class="keyword">if</span> ( !fns )&#123; <span class="comment">// 如果 key 对应的消息没有被人订阅，则直接返回</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">if</span> ( !fn )&#123; <span class="comment">// 如果没有传入具体的回调函数，表示需要取消 key 对应消息的所有订阅</span></span><br><span class="line">        fns &amp;&amp; ( fns.length = <span class="number">0</span> ); </span><br><span class="line">    &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">        <span class="keyword">for</span> ( <span class="keyword">var</span> l = fns.length - <span class="number">1</span>; l &gt;=<span class="number">0</span>; l-- )&#123; <span class="comment">// 反向遍历订阅的回调函数列表</span></span><br><span class="line">            <span class="keyword">var</span> _fn = fns[ l ]; </span><br><span class="line">            <span class="keyword">if</span> ( _fn === fn )&#123; </span><br><span class="line">                fns.splice( l, <span class="number">1</span> ); <span class="comment">// 删除订阅者的回调函数</span></span><br><span class="line">            &#125; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h2 id="全局发布订阅"><a href="#全局发布订阅" class="headerlink" title="全局发布订阅"></a>全局发布订阅</h2><p>回想下刚刚实现的发布—订阅模式，我们给售楼处对象和登录对象都添加了订阅和发布的功 能，这里还存在两个小问题。 </p>
<p>1、 我们给每个发布者对象都添加了 listen 和 trigger 方法，以及一个缓存列表 clientList， 这其实是一种资源浪费。 </p>
<p>2、小明跟售楼处对象还是存在一定的耦合性，小明至少要知道售楼处对象的名字是 salesOffices，才能顺利的订阅到事件。见如下代码：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">salesOffices.listen( <span class="string">'squareMeter100'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>&#123; <span class="comment">// 小明订阅消息</span></span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); </span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>在程序中，发布—订阅模式可以用一个全局的 Event 对象来实现，订阅者不需要了解消 息来自哪个发布者，发布者也不知道消息会推送给哪些订阅者，Event 作为一个类似“中介者” 的角色，把订阅者和发布者联系起来。这也就是最常用的中介者模式：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Event = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="comment">//为了模仿块级作用域，(function()&#123;&#125;)();定义并立即调用一个匿名函数</span></span><br><span class="line">    <span class="keyword">var</span> clientList = &#123;&#125;, </span><br><span class="line">        listen, </span><br><span class="line">        trigger, </span><br><span class="line">        remove; </span><br><span class="line">    listen = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">if</span> ( !clientList[ key ] )&#123; </span><br><span class="line">            clientList[ key ] = []; </span><br><span class="line">        &#125; </span><br><span class="line">        clientList[ key ].push( fn ); </span><br><span class="line">    &#125;; </span><br><span class="line">    trigger = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">var</span> key = <span class="built_in">Array</span>.prototype.shift.call( <span class="built_in">arguments</span> ), </span><br><span class="line">            fns = clientList[ key ]; </span><br><span class="line">        <span class="keyword">if</span> ( !fns || fns.length === <span class="number">0</span> )&#123; </span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">for</span>( <span class="keyword">var</span> i = <span class="number">0</span>, fn; fn = fns[ i++ ]; )&#123; </span><br><span class="line">            fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125;; </span><br><span class="line">    remove = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">var</span> fns = clientList[ key ]; </span><br><span class="line">        <span class="keyword">if</span> ( !fns )&#123; </span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> ( !fn )&#123; </span><br><span class="line">            fns &amp;&amp; ( fns.length = <span class="number">0</span> ); </span><br><span class="line">        &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">            <span class="keyword">for</span> ( <span class="keyword">var</span> l = fns.length - <span class="number">1</span>; l &gt;=<span class="number">0</span>; l-- )&#123; </span><br><span class="line">                <span class="keyword">var</span> _fn = fns[ l ]; </span><br><span class="line">                <span class="keyword">if</span> ( _fn === fn )&#123; </span><br><span class="line">                    fns.splice( l, <span class="number">1</span> ); </span><br><span class="line">                &#125; </span><br><span class="line">            &#125; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        listen: listen, </span><br><span class="line">        trigger: trigger, </span><br><span class="line">        remove: remove </span><br><span class="line">    &#125; </span><br><span class="line">&#125;)(); </span><br><span class="line">Event.listen( <span class="string">'squareMeter88'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>&#123; <span class="comment">// 小红订阅消息</span></span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); <span class="comment">// 输出：'价格=2000000' </span></span><br><span class="line">&#125;); </span><br><span class="line">Event.trigger( <span class="string">'squareMeter88'</span>, <span class="number">2000000</span> ); <span class="comment">// 售楼处发布消息</span></span><br></pre></td></tr></table></figure>

<h2 id="模块间通信"><a href="#模块间通信" class="headerlink" title="模块间通信"></a>模块间通信</h2><p>上一节中实现的发布—订阅模式的实现，是基于一个全局的 Event 对象，我们利用它可以在 两个封装良好的模块中进行通信，这两个模块可以完全不知道对方的存在。就如同有了中介公司 之后，我们不再需要知道房子开售的消息来自哪个售楼处。 比如现在有两个模块，a 模块里面有一个按钮，每次点击按钮之后，b 模块里的 div 中会显示 按钮的总点击次数，我们用全局发布—订阅模式完成下面的代码，使得 a 模块和 b 模块可以在保 持封装性的前提下进行通信</p>
<p>我们所了解到的发布—订阅模式，都是订阅者必须先订阅一个消息，随后才能接收到发布者 发布的消息。如果把顺序反过来，发布者先发布一条消息，而在此之前并没有对象来订阅它，这 条消息无疑将消失在宇宙中。 在某些情况下，我们需要先将这条消息保存下来，等到有对象来订阅它的时候，再重新把消 息发布给订阅者。就如同 QQ 中的离线消息一样，离线消息被保存在服务器中，接收人下次登录 上线之后，可以重新收到这条消息</p>
<p>为了满足这个需求，我们要建立一个存放离线事件的堆栈，当事件发布的时候，如果此时还 没有订阅者来订阅这个事件，我们暂时把发布事件的动作包裹在一个函数里，这些包装函数将被 存入堆栈中，等到终于有对象来订阅此事件的时候，我们将遍历堆栈并且依次执行这些包装函数， 也就是重新发布里面的事件。当然离线事件的生命周期只有一次，就像 QQ 的未读消息只会被重 新阅读一次，所以刚才的操作我们只能进行一次。</p>
<h2 id="命名冲突"><a href="#命名冲突" class="headerlink" title="命名冲突"></a>命名冲突</h2><p>全局的发布—订阅对象里只有一个 clinetList 来存放消息名和回调函数，大家都通过它来订 阅和发布各种消息，久而久之，难免会出现事件名冲突的情况，所以我们还可以给 Event 对象提 供创建命名空间的功能。 在提供最终的代码之前，我们来感受一下怎么使用这两个新增的功能。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/************** 先发布后订阅 ********************/</span> </span><br><span class="line">Event.trigger( <span class="string">'click'</span>, <span class="number">1</span> ); </span><br><span class="line">Event.listen( <span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> a </span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( a ); <span class="comment">// 输出：1 </span></span><br><span class="line">&#125;); </span><br><span class="line"><span class="comment">/************** 使用命名空间 ********************/</span> </span><br><span class="line">Event.create( <span class="string">'namespace1'</span> ).listen( <span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> a </span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( a ); <span class="comment">// 输出：1 </span></span><br><span class="line">&#125;); </span><br><span class="line">Event.create( <span class="string">'namespace1'</span> ).trigger( <span class="string">'click'</span>, <span class="number">1</span> ); </span><br><span class="line">Event.create( <span class="string">'namespace2'</span> ).listen( <span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> a </span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( a ); <span class="comment">// 输出：2 </span></span><br><span class="line">&#125;); </span><br><span class="line">Event.create( <span class="string">'namespace2'</span> ).trigger( <span class="string">'click'</span>, <span class="number">2</span> );</span><br><span class="line"></span><br><span class="line"><span class="comment">//具体实现代码如下：</span></span><br><span class="line"><span class="keyword">var</span> Event = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> global = <span class="keyword">this</span>, </span><br><span class="line">        Event, </span><br><span class="line">        _default = <span class="string">'default'</span>; </span><br><span class="line">    </span><br><span class="line">    Event = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">var</span> _listen, </span><br><span class="line">            _trigger, </span><br><span class="line">            _remove, </span><br><span class="line">            _slice = <span class="built_in">Array</span>.prototype.slice, </span><br><span class="line">            _shift = <span class="built_in">Array</span>.prototype.shift, </span><br><span class="line">            _unshift = <span class="built_in">Array</span>.prototype.unshift, </span><br><span class="line">            namespaceCache = &#123;&#125;, </span><br><span class="line">            _create, </span><br><span class="line">            find, </span><br><span class="line">            each = <span class="function"><span class="keyword">function</span>(<span class="params"> ary, fn </span>)</span>&#123; </span><br><span class="line">                <span class="keyword">var</span> ret; </span><br><span class="line">                <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = ary.length; i &lt; l; i++ )&#123;</span><br><span class="line">                    <span class="keyword">var</span> n = ary[i]; </span><br><span class="line">                    ret = fn.call( n, i, n); </span><br><span class="line">                &#125; </span><br><span class="line">                <span class="keyword">return</span> ret; </span><br><span class="line">            &#125;; </span><br><span class="line">        _listen = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn, cache </span>)</span>&#123; </span><br><span class="line">            <span class="keyword">if</span> ( !cache[ key ] )&#123; </span><br><span class="line">                cache[ key ] = []; </span><br><span class="line">            &#125; </span><br><span class="line">            cache[key].push( fn ); </span><br><span class="line">        &#125;; </span><br><span class="line">        _remove = <span class="function"><span class="keyword">function</span>(<span class="params"> key, cache ,fn</span>)</span>&#123; </span><br><span class="line">            <span class="keyword">if</span> ( cache[ key ] )&#123; </span><br><span class="line">                <span class="keyword">if</span>( fn )&#123; </span><br><span class="line">                    <span class="keyword">for</span>( <span class="keyword">var</span> i = cache[ key ].length; i &gt;= <span class="number">0</span>; i-- )&#123; </span><br><span class="line">                        <span class="keyword">if</span>( cache[ key ][i] === fn )&#123; </span><br><span class="line">                            cache[ key ].splice( i, <span class="number">1</span> ); </span><br><span class="line">                        &#125; </span><br><span class="line">                    &#125; </span><br><span class="line">                &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">                    cache[ key ] = []; </span><br><span class="line">                &#125; </span><br><span class="line">            &#125; </span><br><span class="line">        &#125;; </span><br><span class="line">        _trigger = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">            <span class="keyword">var</span> cache = _shift.call(<span class="built_in">arguments</span>), </span><br><span class="line">                key = _shift.call(<span class="built_in">arguments</span>), </span><br><span class="line">                args = <span class="built_in">arguments</span>, </span><br><span class="line">                _self = <span class="keyword">this</span>, </span><br><span class="line">                ret, </span><br><span class="line">                stack = cache[ key ]; </span><br><span class="line">            <span class="keyword">if</span> ( !stack || !stack.length )&#123; </span><br><span class="line">                <span class="keyword">return</span>; </span><br><span class="line">            &#125; </span><br><span class="line">            <span class="keyword">return</span> each( stack, <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.apply( _self, args ); </span><br><span class="line">            &#125;); </span><br><span class="line">        &#125;; </span><br><span class="line">        _create = <span class="function"><span class="keyword">function</span>(<span class="params"> namespace </span>)</span>&#123; </span><br><span class="line">            <span class="keyword">var</span> namespace = namespace || _default; </span><br><span class="line">            <span class="keyword">var</span> cache = &#123;&#125;, </span><br><span class="line">                offlineStack = [], <span class="comment">// 离线事件 </span></span><br><span class="line">                ret = &#123; </span><br><span class="line">                    listen: <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn, last </span>)</span>&#123; </span><br><span class="line">                        _listen( key, fn, cache ); </span><br><span class="line">                        <span class="keyword">if</span> ( offlineStack === <span class="literal">null</span> )&#123; </span><br><span class="line">                            <span class="keyword">return</span>; </span><br><span class="line">                        &#125; </span><br><span class="line">                        <span class="keyword">if</span> ( last === <span class="string">'last'</span> )&#123;</span><br><span class="line">                            offlineStack.length &amp;&amp; offlineStack.pop()(); </span><br><span class="line">                        &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">                            each( offlineStack, <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">                                <span class="keyword">this</span>(); </span><br><span class="line">                            &#125;); </span><br><span class="line">                        &#125; </span><br><span class="line">                        offlineStack = <span class="literal">null</span>; </span><br><span class="line">                    &#125;, </span><br><span class="line">                    one: <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn, last </span>)</span>&#123; </span><br><span class="line">                        _remove( key, cache ); </span><br><span class="line">                        <span class="keyword">this</span>.listen( key, fn ,last ); </span><br><span class="line">                    &#125;, </span><br><span class="line">                    remove: <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>&#123; </span><br><span class="line">                        _remove( key, cache ,fn); </span><br><span class="line">                    &#125;, </span><br><span class="line">                    trigger: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">                        <span class="keyword">var</span> fn, </span><br><span class="line">                            args, </span><br><span class="line">                            _self = <span class="keyword">this</span>; </span><br><span class="line">                        _unshift.call( <span class="built_in">arguments</span>, cache ); </span><br><span class="line">                        args = <span class="built_in">arguments</span>; </span><br><span class="line">                        fn = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">                            <span class="keyword">return</span> _trigger.apply( _self, args ); </span><br><span class="line">                        &#125;; </span><br><span class="line">                        <span class="keyword">if</span> ( offlineStack )&#123; </span><br><span class="line">                            <span class="keyword">return</span> offlineStack.push( fn ); </span><br><span class="line">                        &#125; </span><br><span class="line">                        <span class="keyword">return</span> fn(); </span><br><span class="line">                    &#125; </span><br><span class="line">                &#125;; </span><br><span class="line">            <span class="keyword">return</span> namespace ? ( namespaceCache[ namespace ] ? namespaceCache[ namespace ] : </span><br><span class="line"> namespaceCache[ namespace ] = ret ) : ret; </span><br><span class="line">        &#125;; </span><br><span class="line">        <span class="keyword">return</span> &#123; </span><br><span class="line">            create: _create, </span><br><span class="line">            one: <span class="function"><span class="keyword">function</span>(<span class="params"> key,fn, last </span>)</span>&#123; </span><br><span class="line">                <span class="keyword">var</span> event = <span class="keyword">this</span>.create( ); </span><br><span class="line">                event.one( key,fn,last ); </span><br><span class="line">            &#125;, </span><br><span class="line">            remove: <span class="function"><span class="keyword">function</span>(<span class="params"> key,fn </span>)</span>&#123; </span><br><span class="line">                <span class="keyword">var</span> event = <span class="keyword">this</span>.create( ); </span><br><span class="line">                event.remove( key,fn ); </span><br><span class="line">            &#125;, </span><br><span class="line">            listen: <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn, last </span>)</span>&#123; </span><br><span class="line">                <span class="keyword">var</span> event = <span class="keyword">this</span>.create( ); </span><br><span class="line">                event.listen( key, fn, last ); </span><br><span class="line">            &#125;, </span><br><span class="line">            trigger: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">                <span class="keyword">var</span> event = <span class="keyword">this</span>.create( );</span><br><span class="line">                event.trigger.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); </span><br><span class="line">            &#125; </span><br><span class="line">        &#125;; </span><br><span class="line">    &#125;(); </span><br><span class="line">    <span class="keyword">return</span> Event; </span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure>

<h2 id="便利性"><a href="#便利性" class="headerlink" title="便利性"></a>便利性</h2><p>我们一直讨论的发布—订阅模式，跟一些别的语言（比如 Java）中的实现 还是有区别的。在 Java 中实现一个自己的发布—订阅模式，通常会把订阅者对象自身当成引用传 入发布者对象中，同时订阅者对象还需提供一个名为诸如 update 的方法，供发布者对象在适合的 时候调用。而在 JavaScript 中，我们用注册回调函数的形式来代替传统的发布—订阅模式，显得更 加优雅和简单。 </p>
<p>另外，在 JavaScript 中，我们无需去选择使用推模型还是拉模型。推模型是指在事件发生时， 发布者一次性把所有更改的状态和数据都推送给订阅者。拉模型不同的地方是，发布者仅仅通知 订阅者事件已经发生了，此外发布者要提供一些公开的接口供订阅者来主动拉取数据。拉模型的 好处是可以让订阅者“按需获取”，但同时有可能让发布者变成一个“门户大开”的对象，同时 增加了代码量和复杂度</p>
<p>刚好在 JavaScript 中，arguments 可以很方便地表示参数列表，所以我们一般都会选择推模型， 使用 Function.prototype.apply 方法把所有参数都推送给订阅者。</p>
<p>发布—订阅模式的优点非常明显，一为时间上的解耦，二为对象之间的解耦。它的应用非常 广泛，既可以用在异步编程中，也可以帮助我们完成更松耦合的代码编写。发布—订阅模式还可 以用来帮助实现一些别的设计模式，比如中介者模式。从架构上来看，无论是 MVC 还是 MVVM， 都少不了发布—订阅模式的参与，而且 JavaScript 本身也是一门基于事件驱动的语言。  </p>
<p>当然，发布—订阅模式也不是完全没有缺点。创建订阅者本身要消耗一定的时间和内存，而 且当你订阅一个消息后，也许此消息最后都未发生，但这个订阅者会始终存在于内存中。另外， 发布—订阅模式虽然可以弱化对象之间的联系，但如果过度使用的话，对象和对象之间的必要联 系也将被深埋在背后，会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一 起的时候，要跟踪一个 bug 不是件轻松的事情。</p>
<h1 id="命令模式"><a href="#命令模式" class="headerlink" title="命令模式"></a>命令模式</h1><p>假设有一个快餐店，而我是该餐厅的点餐服务员，那么我一天的工作应该是这样的：当某位 客人点餐或者打来订餐电话后，我会把他的需求都写在清单上，然后交给厨房，客人不用关心是 哪些厨师帮他炒菜。我们餐厅还可以满足客人需要的定时服务，比如客人可能当前正在回家的路 上，要求 1 个小时后才开始炒他的菜，只要订单还在，厨师就不会忘记。客人也可以很方便地打 电话来撤销订单。另外如果有太多的客人点餐，厨房可以按照订单的顺序排队炒菜。 这些记录着订餐信息的清单，便是命令模式中的命令对象。</p>
<p>命令模式是最简单和优雅的模式之一，命令模式中的命令（command）指的是一个执行某些 特定事情的指令。 命令模式最常见的应用场景是：有时候需要向某些对象发送请求，但是并不知道请求的接收 者是谁，也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序，使得请求发送者和请求接收者能够消除彼此之间的耦合关系。</p>
<p>命令模 式将过程式的请求调用封装在 command 对象的 execute 方法里，通过封装方法调用，我们可以把 运算块包装成形。command 对象可以被四处传递，所以在调用命令的时候，客户（Client）不需要 关心事情是如何进行的。 </p>
<p>命令模式的由来，其实是回调（callback）函数的一个面向对象的替代品。</p>
<p>JavaScript 作为将函数作为一等对象的语言，跟策略模式一样，命令模式也早已融入到了 JavaScript 语言之中。运算块不一定要封装在 command.execute 方法中，也可以封装在普通函数中。 函数作为一等对象，本身就可以被四处传递。即使我们依然需要请求“接收者”，那也未必使用 面向对象的方式，闭包可以完成同样的功能。 </p>
<p>在面向对象设计中，命令模式的接收者被当成 command 对象的属性保存起来，同时约定执行 命令的操作调用 command.execute 方法。在使用闭包的命令模式实现中，接收者被封闭在闭包产 生的环境中，执行命令的操作可以更加简单，仅仅执行回调函数即可。无论接收者被保存为对象 的属性，还是被封闭在闭包产生的环境中，在将来执行命令的时候，接收者都能被顺利访问。用 闭包实现的命令模式如下代码所示：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> setCommand = <span class="function"><span class="keyword">function</span>(<span class="params"> button, func </span>)</span>&#123; </span><br><span class="line">    button.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        func(); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> MenuBar = &#123; </span><br><span class="line">    refresh: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'刷新菜单界面'</span> ); </span><br><span class="line">    &#125;</span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> RefreshMenuBarCommand = <span class="function"><span class="keyword">function</span>(<span class="params"> receiver </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">            receiver.refresh();</span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> setCommand = <span class="function"><span class="keyword">function</span>(<span class="params"> button, command </span>)</span>&#123; </span><br><span class="line">    button.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        command.execute(); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> refreshMenuBarCommand = RefreshMenuBarCommand( MenuBar ); </span><br><span class="line">setCommand( button1, refreshMenuBarCommand );</span><br></pre></td></tr></table></figure>

<h2 id="撤销与重做"><a href="#撤销与重做" class="headerlink" title="撤销与重做"></a>撤销与重做</h2><p>节我们讨论了如何撤销一个命令。很多时候，我们需要撤销一系列的命令。比如在一个 围棋程序中，现在已经下了 10 步棋，我们需要一次性悔棋到第 5 步。在这之前，我们可以把所 有执行过的下棋命令都储存在一个历史列表中，然后倒序循环来依次执行这些命令的 undo 操作， 直到循环执行到第 5 个命令为止。 </p>
<p>然而，在某些情况下无法顺利地利用 undo 操作让对象回到 execute 之前的状态。比如在一个 Canvas 画图的程序中，画布上有一些点，我们在这些点之间画了 N 条曲线把这些点相互连接起 来，当然这是用命令模式来实现的。但是我们却很难为这里的命令对象定义一个擦除某条曲线的 undo 操作，因为在 Canvas 画图中，擦除一条线相对不容易实现。</p>
<p>这时候最好的办法是先清除画布，然后把刚才执行过的命令全部重新执行一遍，这一点同样 可以利用一个历史列表堆栈办到。记录命令日志，然后重复执行它们，这是逆转不可逆命令的一 个好办法</p>
<h2 id="命令队列"><a href="#命令队列" class="headerlink" title="命令队列"></a>命令队列</h2><p>队列在动画中的运用场景也非常多，比如之前的小球运动程序有可能遇到另外一个问题：有 些用户反馈，这个程序只适合于 APM 小于 20 的人群，大部分用户都有快速连续点击按钮的习惯， 当用户第二次点击 button 的时候，此时小球的前一个动画可能尚未结束，于是前一个动画会骤然 停止，小球转而开始第二个动画的运动过程。但这并不是用户的期望，用户希望这两个动画会排 队进行。 </p>
<p>把请求封装成命令对象的优点在这里再次体现了出来，对象的生命周期几乎是永久的，除非 我们主动去回收它。也就是说，命令对象的生命周期跟初始请求发生的时间无关，command 对象 的 execute 方法可以在程序运行的任何时刻执行，即使点击按钮的请求早已发生，但我们的命令 对象仍然是有生命的。 所以我们可以把 div 的这些运动过程都封装成命令对象，再把它们压进一个队列堆栈，当动 画执行完，也就是当前 command 对象的职责完成之后，会主动通知队列，此时取出正在队列中等 待的第一个命令对象，并且执行它。</p>
<p>我们比较关注的问题是，一个动画结束后该如何通知队列。通常可以使用回调函数来通知队 列，除了回调函数之外，还可以选择发布订阅模式。即在一个动画结束后发布一个消息，订阅 者接收到这个消息之后，便开始执行队列里的下一个动画。读者可以尝试按照这个思路来自行实 现一个队列动画</p>
<p>宏命令是一组命令的集合，通过执行宏命令的方式，可以一次执行一批命令。想象一下，家 里有一个万能遥控器，每天回家的时候，只要按一个特别的按钮，它就会帮我们关上房间门，顺 便打开电脑并登录 QQ</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本章我们学习了命令模式。跟许多其他语言不同，JavaScript 可以用高阶函数非常方便地实 现命令模式。命令模式在 JavaScript 语言中是一种隐形的模式</p>

    </div>
    
    
    
    
    <div>
      
        <div>
    
        <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div>
    
</div>

      
    </div>
        <div class="reward-container">
  <div>坚持原创技术分享，您的支持将鼓励我继续创作！</div>
  <button onclick="var qr = document.getElementById('qr'); qr.style.display = (qr.style.display === 'none') ? 'block' : 'none';">
    打赏
  </button>
  <div id="qr" style="display: none;">
      
      <div style="display: inline-block;">
        <img src="/images/alipay.png" alt="Li Yudong 支付宝">
        <p>支付宝</p>
      </div>

  </div>
</div>

        

<div>
<ul class="post-copyright">
  <li class="post-copyright-author">
    <strong>本文作者： </strong>Li Yudong
  </li>
  <li class="post-copyright-link">
    <strong>本文链接：</strong>
    <a href="http://example.com/2021/10/31/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%BA%8C%EF%BC%89/" title="JavaScript的设计模式（二）">http://example.com/2021/10/31/设计模式/JavaScript的设计模式（二）/</a>
  </li>
  <li class="post-copyright-license">
    <strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="noopener" target="_blank"><i class="fab fa-fw fa-creative-commons"></i>BY-NC-SA</a> 许可协议。转载请注明出处！
  </li>
</ul>
</div>

    
      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" rel="tag"># 设计模式</a>
          </div>
    
        

    
        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2021/10/28/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%B8%80%EF%BC%89/" rel="prev" title="JavaScript的设计模式（一）">
      <i class="fa fa-chevron-left"></i> JavaScript的设计模式（一）
    </a></div>
      <div class="post-nav-item">
    <a href="/2021/11/03/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%B8%89%EF%BC%89/" rel="next" title="JavaScript的设计模式（三）">
      JavaScript的设计模式（三） <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#前言"><span class="nav-number">1.</span> <span class="nav-text">前言</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#设计模式原则"><span class="nav-number">2.</span> <span class="nav-text">设计模式原则</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#单一职责原则"><span class="nav-number">2.1.</span> <span class="nav-text">单一职责原则</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#开放封闭原则"><span class="nav-number">2.2.</span> <span class="nav-text">开放封闭原则</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#里氏代换原则"><span class="nav-number">2.3.</span> <span class="nav-text">里氏代换原则</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#依赖倒转原则"><span class="nav-number">2.4.</span> <span class="nav-text">依赖倒转原则</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#接口隔离原则"><span class="nav-number">2.5.</span> <span class="nav-text">接口隔离原则</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#迪米特法则"><span class="nav-number">2.6.</span> <span class="nav-text">迪米特法则</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#迭代器模式"><span class="nav-number">3.</span> <span class="nav-text">迭代器模式</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#内部、外部迭代器"><span class="nav-number">3.1.</span> <span class="nav-text">内部、外部迭代器</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#内部迭代"><span class="nav-number">3.1.1.</span> <span class="nav-text">内部迭代</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#外部迭代"><span class="nav-number">3.1.2.</span> <span class="nav-text">外部迭代</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#迭代器应用"><span class="nav-number">3.2.</span> <span class="nav-text">迭代器应用</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#发布订阅模式"><span class="nav-number">4.</span> <span class="nav-text">发布订阅模式</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#通用实现"><span class="nav-number">4.1.</span> <span class="nav-text">通用实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#全局发布订阅"><span class="nav-number">4.2.</span> <span class="nav-text">全局发布订阅</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#模块间通信"><span class="nav-number">4.3.</span> <span class="nav-text">模块间通信</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#命名冲突"><span class="nav-number">4.4.</span> <span class="nav-text">命名冲突</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#便利性"><span class="nav-number">4.5.</span> <span class="nav-text">便利性</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#命令模式"><span class="nav-number">5.</span> <span class="nav-text">命令模式</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#撤销与重做"><span class="nav-number">5.1.</span> <span class="nav-text">撤销与重做</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#命令队列"><span class="nav-number">5.2.</span> <span class="nav-text">命令队列</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#总结"><span class="nav-number">5.3.</span> <span class="nav-text">总结</span></a></li></ol></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="Li Yudong"
      src="/images/author.jpg">
  <p class="site-author-name" itemprop="name">Li Yudong</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">95</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">14</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">18</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/tiarmor1" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;tiarmor1" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:1157019137@qq.com" title="E-Mail → mailto:1157019137@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>
  <div class="cc-license motion-element" itemprop="license">
    <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" class="cc-opacity" rel="noopener" target="_blank"><img src="/images/cc-by-nc-sa.svg" alt="Creative Commons"></a>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 2020 – 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Li Yudong</span>
</div>

        








      </div>
    </footer>
  </div>

  
  
  <script color='0,0,0' opacity='0.5' zIndex='-1' count='150' src="/lib1/canvas-nest/canvas-nest.min.js"></script>
  <script src="/lib1/anime.min.js"></script>
  <script src="/lib1/velocity/velocity.min.js"></script>
  <script src="/lib1/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  




  
<script src="/js/local-search.js"></script>













  

  

  
   <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas> 
   <script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script> 
   <script type="text/javascript" src="/js/fireworks.js"></script>
  
</body>
</html>

